﻿using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Newtonsoft.Json;
using System.IO;
using System.Windows.Forms;
using System.Security;




namespace COMCardmanager
{

    /// <summary>
    /// call CARD_Initialize method from SDK (cardmanager.dll)
    /// </summary>
    /// <param name="param">XML filename containing settings</param>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]    
    public delegate void CARD_initialize(String param);
    /// <summary>
    /// call card_read_data from SDK (cardmanager.dll)
    /// </summary>
    /// <param name="cardType">PC or CG</param>
    /// <param name="action">should be READ</param>
    /// <returns>data as xml format</returns>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate bool CARD_read_data(String cardType, String action);
    /// <summary>
    /// new method to read public data (without writing local file)
    /// </summary>
    /// <param name="cardType">PC or CG</param>
    /// <param name="data">should be READ</param>
    /// <returns>data as XML format</returns>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int read_public_data2(String cardType, StringBuilder data);
   /// <summary>
   /// external CARD_init_reader_cycle (from SDK); initialize a cycle of card read
   /// </summary>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_init_reader_cycle();
    /// <summary>
    /// reset cycle of card read from SDK (external call)
    /// </summary>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_reset_reader_cycle();
    /// <summary>
    /// external call (from SDK) to terminate read operation
    /// </summary>
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CARD_terminate();

    /// <summary>
    /// public class that handle and provide methods to read a PC and CG
    /// </summary>
    [ComVisible(true)]
    [Guid("3B835D63-ABFE-413a-9F3B-20329A2CDEEE")]
    [ComSourceInterfaces(typeof(ICard))] // Interface to use 
    [ClassInterface(ClassInterfaceType.AutoDispatch)]
    public class Card:COMCardmanager.ICard
    {
        #region constants
        private const String CARDREADSUCCESSFULLY = @"Card read successfully";
        private const String CARDMANAGER = "CardManager.dll";
        private const String INSTDIR = @"COMCardManagerInstDir";
        private const String PC = @"PC";
        private const String CG = @"CG";
        private const int MAXLEN = 10000; //max 10 Ko used by chip
        private const String ACTION_READ = @"READ";
        private const String DEFAULTPARAM = @"-x C:\facil\SDK\Lib\ConfigChipMgrCardManager.xml -a CARDApp -i CARDInstance";
        private const String UNABLETOREADDATA = @"ERR001: Unable to read card...";
        private const String UNABLETOLOADLIBRARY = @"ERR002: Unable to load SDK library...";
        private const String UNABLETOINITIALIZELIBRARY = @"ERR003: Unable to initialize SDK library...";
        private const String UNABLETOFREEMEMORY = @"ERR004:Unable to free memory...";
        private const String UNABLETOINITREADERCYCLE = @"ERR005:Unable to initialize cycle of chip read...Please check your configuration files.";
        private const String UNABLETORESETREADERCYCLE = @"ERR006:Unable to reset cycle of chip read...";
        private const String UNABLETOTERMINATE = @"ERR008:Unable to terminate connexion with chip...";
        private const String NOVALIDCERTIFICATE = @"ERR007: No valid certificate available in personnal certificate 
store...";

        #endregion

        private String PARAM = DEFAULTPARAM;
        /// <summary>
        /// parameter that allow to set and get value of full pathname of configuration xml file to use by local SDK
        /// </summary>
        public String XMLSettingsToLoad
        {
            get { return PARAM; }
            set { PARAM = value; }
        }


        /// <summary>
        /// define xml file that cardmanager should load and use
        /// </summary>
        /// <param name="szFilename"></param>
        public void setConfigToLoad(String szFilename)
        {


            if (File.Exists(szFilename))
            {
                XMLSettingsToLoad = "-x " + szFilename + " -a CARDApp -i CARDInstance";
            }
            else
            {
                string message = szFilename + " does not exist on your computer; please check your settings and call your administrator.";            
                MessageBoxButtons buttons = MessageBoxButtons.OK;
                DialogResult result;

                // Displays the MessageBox.
                result = MessageBox.Show(message, "Configuration file not found on local computer", buttons);

            }

            


        }
        /// <summary>
        /// default constructor
        /// </summary>
        public Card()
        {
            //default constructor
        }
        /// <summary>
        /// read automobile registration chip
        /// </summary>
        /// <returns>data in json format</returns>
        public String readCG(bool isDumped=false)
        {
            try
            {
                return readCard(CG,isDumped);
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }

        /// <summary>
        /// read a driver license chip
        /// </summary>
        /// <returns>data read from chip in json format</returns>
        public String readPC(bool isDumped = false)
        {
            try
            {
                return readCard(PC,isDumped);
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }



        private String readCard(String cardType, bool isDumped = false)
        {
            String result = "";
            IntPtr hDll = IntPtr.Zero;
            IntPtr addr = IntPtr.Zero;
            bool isRead = false;
            string szData = "";
            
            //define if we need to free memory (after loading successfully in memory) or not
            bool isFree = true;
            String info = "";
            
           
            try
            {
               
                String instDir = @"c:\Facil\SDK\Lib";
                instDir = GetEnvironmentVariable(instDir);
               
                
                //call CARDMANAGER.DLl library
                //Console.WriteLine("loading library");

                #region loadlibrary
                hDll = NativeMethods.LoadLibrary(CARDMANAGER);
                if (hDll.Equals(IntPtr.Zero))
                {
                    isFree = true;
                    throw new Exception(UNABLETOLOADLIBRARY + CARDMANAGER);
                }
                else
                {
                    info = CARDMANAGER + " loaded successfully";
                    isFree = false;
                }
                #endregion //loadlibrary

                #region CARD_initialize
                //try to call CARD_initialize
                try
                {
                    info += "CARD_initialize (" + XMLSettingsToLoad + ") ";
                    addr = NativeMethods.GetProcAddress(hDll, "CARD_initialize");
                    CARD_initialize _CARD_initialize = (CARD_initialize)Marshal.GetDelegateForFunctionPointer(
                                                                                                addr,
                                                                                                typeof(CARD_initialize));

                    _CARD_initialize(PARAM);
                    result += "CARD_initialized successfully...\n";
                }
                catch (Exception e)
                {
                    info += "ERROR CARDInitialize "+ e.Message;
                    throw new Exception(UNABLETOINITIALIZELIBRARY + CARDMANAGER);
                }
                #endregion

                #region start cycle of chip read
                //start a reading cycle
                try
                {
                    addr = NativeMethods.GetProcAddress(hDll, "CARD_init_reader_cycle");
                    CARD_init_reader_cycle _CARD_init_reader_cycle = (CARD_init_reader_cycle)Marshal.GetDelegateForFunctionPointer(
                                                                                                addr,
                                                                                                typeof(CARD_init_reader_cycle));

                    _CARD_init_reader_cycle();
                    result += "init_reader_cycle successfully...";
                }
                catch (Exception e)
                {
                    info += "ERROR CARD_init_reader_cycle" + e.Message +e.StackTrace;
                    throw new Exception(UNABLETOINITREADERCYCLE);
                }
                #endregion

                #region read_chip
                //try to read card
                try
                {
                    //Console.WriteLine("reading card");
                    addr = NativeMethods.GetProcAddress(hDll, "read_public_data2");
                    read_public_data2 _read_data2 = (read_public_data2)Marshal.GetDelegateForFunctionPointer(
                                                                                                addr,
                                                                                                typeof(read_public_data2));




                    StringBuilder data = new StringBuilder(MAXLEN);
                    data.Clear();
                    int len = _read_data2(cardType, data);
                    result += "nb char returned=" + len;
                    

                    //String szDataConverted = myConvert(szData);

                    if (len > 0)
                    {
                        szData = data.ToString(0, len - 1);                                        
                        info += " read successfull..." + cardType;
                        isRead = true;
                    }
                    else
                    {
                        //Console.WriteLine("ERROR: unable to read card...");
                        result += UNABLETOREADDATA;
                    }
                }
                catch (Exception e)
                {
                    result += UNABLETOREADDATA + e.Message + e.InnerException + e.StackTrace;
                }                
                #endregion //read chip
                #region reset_cycle
                //reset reading cycle
                try
                {
                    addr = NativeMethods.GetProcAddress(hDll, "CARD_reset_reader_cycle");
                    CARD_reset_reader_cycle _CARD_reset_reader_cycle = (CARD_reset_reader_cycle)Marshal.GetDelegateForFunctionPointer(
                                                                                                addr,
                                                                                                typeof(CARD_reset_reader_cycle));

                    _CARD_reset_reader_cycle();
                    result += "\n reset_reader_cycle successfully...";
                }
                catch (Exception e)
                {
                    result += "ERROR: CARD_reset_reader_cycle" + e.Message + e.InnerException + e.StackTrace; ;
                }
                #endregion
                #region terminate
                try
                {
                    //CARD_terminate                   
                    addr = NativeMethods.GetProcAddress(hDll, "CARD_terminate");
                    CARD_terminate _CARD_terminate = (CARD_terminate)Marshal.GetDelegateForFunctionPointer(
                                                                                                addr,
                                                                                                typeof(CARD_terminate));

                    _CARD_terminate();
                    result += "\n terminate successfully...";
                }
                catch (Exception e)
                {
                    info += "ERROR: CARD_terminate" + e.Message + e.InnerException + e.StackTrace;
                    throw new Exception(UNABLETOTERMINATE);
                }
                #endregion

                freeMemory(ref result, hDll, ref isFree);
                
                #region prepare_data
                if (isRead)
                {
                    //try to convert xml in JSON format                    
                    XmlDocument doc = new XmlDocument();
                    Encoding enc = Encoding.Default;
                    string asciiString = convertTo(szData, enc);                 
                    MemoryStream stream = new MemoryStream(enc.GetBytes(asciiString));
                    stream.Flush();
                    stream.Position = 0;
                    doc.XmlResolver = null;
                    doc.Load(stream);
                    //doc.LoadXml(Encoding.UTF8.GetString(convertToUTF8(szData)));
                    result = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.Indented);
                    
                    dump(isDumped, szData, doc);
                                       
                }
                else
                {
                    throw new Exception(UNABLETOREADDATA);
                }
                #endregion


                return result;
            }
            catch (Exception e)
            {

                info += "ERROR:" + result + " ...ERROR>" + e.Message + e.StackTrace;
                isFree = freeMemory(ref result, hDll, ref isFree);
                return e.Message+e.StackTrace;
            }
            finally
            {
                //free memory by unloading dll
               
            }
        }
        /// <summary>
        /// try to read environment variable
        /// set currentdir to instDir (read in environment variables or passed 
        /// as default parameter in exception case
        /// </summary>
        /// <param name="instDir">default value to use</param>
        /// <returns>value read or default value if exception</returns>
        private String GetEnvironmentVariable(String instDir)
        {
            //try to get environment variable
            try
            {
                instDir = Environment.GetEnvironmentVariable(INSTDIR);
                setConfigToLoad(instDir + @"\ConfigChipMgrCardManager.xml");
            }
            catch (ArgumentNullException ane)
            {
                //variable is not set
                //use default value in this case

            }
            catch (SecurityException se)
            {
                //user does not have right to read environment variable
                //use default value in this case
            }
            finally
            {
                Environment.CurrentDirectory = instDir;
            }
            return instDir;
        }
        /// <summary>
        /// try to dump data in file
        /// </summary>
        /// <param name="isDumped"></param>
        /// <param name="szData"></param>
        /// <param name="doc"></param>
        private void dump(bool isDumped, string szData, XmlDocument doc)
        {
            if (isDumped)
            {              
                Encoding ascii = Encoding.Default;
                Encoding unicode = Encoding.Unicode;
                Encoding utf8 = Encoding.UTF8;
                Encoding w1256 = Encoding.GetEncoding(1256);



               
                string asciiString = convertTo(szData,ascii);
                string utf8String = convertTo(szData, utf8);
                string w1256String = convertTo(szData, w1256);
                
                TextWriter tw = new StreamWriter(@"dumpASCII.xml", true, ascii);
                tw.Write(asciiString);
                tw.Close();

                tw = new StreamWriter(@"dumpUTF8.xml", true, utf8);
                tw.Write(utf8String);
                tw.Close();

                tw = new StreamWriter(@"dump1256.xml", true, w1256);
                tw.Write(w1256String);
                tw.Close();

                tw = new StreamWriter(@"dumpUnicode.xml", true, unicode);
                tw.Write(szData);
                tw.Close();

                
            }
        }

        
       /// <summary>
       /// convert a unicode string to enc format 
       /// </summary>
       /// <param name="szData">unicode string</param>
       /// <param name="enc">define destination encoding to use</param>
       /// <returns>encoding string</returns>
        private String convertTo(string szData, Encoding enc)
        {
            Encoding ascii = enc;
            Encoding unicode = Encoding.Unicode;

            byte[] unicodeBytes = unicode.GetBytes(szData);

            // Perform the conversion from one encoding to the other.
            byte[] asciiBytes = Encoding.Convert(unicode, ascii, unicodeBytes);

            char[] asciiChars = new char[ascii.GetCharCount(asciiBytes, 0, asciiBytes.Length)];

            ascii.GetChars(asciiBytes, 0, asciiBytes.Length, asciiChars, 0);
            string asciiString = new string(asciiChars);

            return asciiString;
        }
        /// <summary>
        /// free memory
        /// </summary>
        /// <param name="result"></param>
        /// <param name="hDll"></param>
        /// <param name="isFree"></param>
        /// <returns>true if memory is free</returns>
        private static bool freeMemory(ref String result, IntPtr hDll, ref bool isFree)
        {
            try
            {
                if (!isFree)
                    isFree = NativeMethods.FreeLibrary(hDll);

                if (!isFree)
                {
                    result += UNABLETOFREEMEMORY;
                }
                else
                {
                    result += "\n free memory successfully...";
                }
                return isFree;
            }
            catch (Exception e)
            {
                throw new Exception(UNABLETOFREEMEMORY + e.Message + e.StackTrace);
            }
        }


    }
}
